﻿$import( "SmitearWebGL.Core.Math.Vector2" );
$import( "SmitearWebGL.Core.Math.Vector3" );
$import( "SmitearWebGL.Core.Math.Vector4" );

SmitearWebGL.Core.Math.Matrix4x4 = Class.extend(
{
	/* * * * * * * * constructor * * * * * * * */

	init : function( data )
	{
		this.reset( data );
		
		this._registClass( "SmitearWebGL.Core.Math.Matrix4x4" );
	}
	,

	/******** public method ********/

	reset : function( data )
	{
		if ( data == undefined || data == null )
		{
			this.identity();
			return;
		}
		else if( data.length != 4 ||
		data[0].length != 4 ||
		data[1].length != 4 ||
		data[2].length != 4 ||
		data[3].length != 4 )
		{
			$THROW( false, "矩阵数据无效" );
		}
		
		this.n11 = data[0][0];
		this.n12 = data[0][1];
		this.n13 = data[0][2];
		this.n14 = data[0][3];
		this.n21 = data[1][0];
		this.n22 = data[1][1];
		this.n23 = data[1][2];
		this.n24 = data[1][3];
		this.n31 = data[2][0];
		this.n32 = data[2][1];
		this.n33 = data[2][2];
		this.n34 = data[2][3];
		this.n41 = data[3][0];
		this.n42 = data[3][1];
		this.n43 = data[3][2];
		this.n44 = data[3][3];
	}
	,
	getData : function()
	{
		var data = new Array( 4 );
		data[0] = new Array( 4 );
		data[1] = new Array( 4 );
		data[2] = new Array( 4 );
		data[3] = new Array( 4 );
		
		data[0][0] = this.n11;
		data[0][1] = this.n12;
		data[0][2] = this.n13;
		data[0][3] = this.n14;
		data[1][0] = this.n21;
		data[1][1] = this.n22;
		data[1][2] = this.n23;
		data[1][3] = this.n24;
		data[2][0] = this.n31;
		data[2][1] = this.n32;
		data[2][2] = this.n33;
		data[2][3] = this.n34;
		data[3][0] = this.n41;
		data[3][1] = this.n42;
		data[3][2] = this.n43;
		data[3][3] = this.n44;
		
		return data;
	}
	,
	//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * OPERATIONS * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

	calculateMultiply : function( a, b )
	{
		var a11 = a.n11;
		var b11 = b.n11;
		var a21 = a.n12;
		var b21 = b.n12;
		var a31 = a.n13;
		var b31 = b.n13;
		var a12 = a.n21;
		var b12 = b.n21;
		var a22 = a.n22;
		var b22 = b.n22;
		var a32 = a.n23;
		var b32 = b.n23;
		var a13 = a.n31;
		var b13 = b.n31;
		var a23 = a.n32;
		var b23 = b.n32;
		var a33 = a.n33;
		var b33 = b.n33;
		var a14 = a.n41;
		var b14 = b.n41;
		var a24 = a.n42;
		var b24 = b.n42;
		var a34 = a.n43;
		var b34 = b.n43;

		this.n11 = a11 * b11 + a12 * b21 + a13 * b31;
		this.n21 = a11 * b12 + a12 * b22 + a13 * b32;
		this.n31 = a11 * b13 + a12 * b23 + a13 * b33;
		this.n41 = a11 * b14 + a12 * b24 + a13 * b34 + a14;

		this.n12 = a21 * b11 + a22 * b21 + a23 * b31;
		this.n22 = a21 * b12 + a22 * b22 + a23 * b32;
		this.n32 = a21 * b13 + a22 * b23 + a23 * b33;
		this.n42 = a21 * b14 + a22 * b24 + a23 * b34 + a24;

		this.n13 = a31 * b11 + a32 * b21 + a33 * b31;
		this.n23 = a31 * b12 + a32 * b22 + a33 * b32;
		this.n33 = a31 * b13 + a32 * b23 + a33 * b33;
		this.n43 = a31 * b14 + a32 * b24 + a33 * b34 + a34;
	}
	,
	calculateMultiply3x3 : function( a, b )
	{
		var a11 = a.n11;
		var b11 = b.n11;
		var a21 = a.n12;
		var b21 = b.n12;
		var a31 = a.n13;
		var b31 = b.n13;
		var a12 = a.n21;
		var b12 = b.n21;
		var a22 = a.n22;
		var b22 = b.n22;
		var a32 = a.n23;
		var b32 = b.n23;
		var a13 = a.n31;
		var b13 = b.n31;
		var a23 = a.n32;
		var b23 = b.n32;
		var a33 = a.n33;
		var b33 = b.n33;

		this.n11 = a11 * b11 + a12 * b21 + a13 * b31;
		this.n21 = a11 * b12 + a12 * b22 + a13 * b32;
		this.n31 = a11 * b13 + a12 * b23 + a13 * b33;

		this.n12 = a21 * b11 + a22 * b21 + a23 * b31;
		this.n22 = a21 * b12 + a22 * b22 + a23 * b32;
		this.n32 = a21 * b13 + a22 * b23 + a23 * b33;

		this.n13 = a31 * b11 + a32 * b21 + a33 * b31;
		this.n23 = a31 * b12 + a32 * b22 + a33 * b32;
		this.n33 = a31 * b13 + a32 * b23 + a33 * b33;
	}
	,
	calculateMultiply4x4 : function( a, b )
	{
		var a11 = a.n11;
		var b11 = b.n11;
		var a21 = a.n12;
		var b21 = b.n12;
		var a31 = a.n13;
		var b31 = b.n13;
		var a41 = a.n14;
		var b41 = b.n14;

		var a12 = a.n21;
		var b12 = b.n21;
		var a22 = a.n22;
		var b22 = b.n22;
		var a32 = a.n23;
		var b32 = b.n23;
		var a42 = a.n24;
		var b42 = b.n24;

		var a13 = a.n31;
		var b13 = b.n31;
		var a23 = a.n32;
		var b23 = b.n32;
		var a33 = a.n33;
		var b33 = b.n33;
		var a43 = a.n34;
		var b43 = b.n34;

		var a14 = a.n41;
		var b14 = b.n41;
		var a24 = a.n42;
		var b24 = b.n42;
		var a34 = a.n43;
		var b34 = b.n43;
		var a44 = a.n44;
		var b44 = b.n44;

		this.n11 = a11 * b11 + a12 * b21 + a13 * b31;
		this.n21 = a11 * b12 + a12 * b22 + a13 * b32;
		this.n31 = a11 * b13 + a12 * b23 + a13 * b33;
		this.n41 = a11 * b14 + a12 * b24 + a13 * b34 + a14;

		this.n12 = a21 * b11 + a22 * b21 + a23 * b31;
		this.n22 = a21 * b12 + a22 * b22 + a23 * b32;
		this.n32 = a21 * b13 + a22 * b23 + a23 * b33;
		this.n42 = a21 * b14 + a22 * b24 + a23 * b34 + a24;

		this.n13 = a31 * b11 + a32 * b21 + a33 * b31;
		this.n23 = a31 * b12 + a32 * b22 + a33 * b32;
		this.n33 = a31 * b13 + a32 * b23 + a33 * b33;
		this.n43 = a31 * b14 + a32 * b24 + a33 * b34 + a34;

		this.n14 = a41 * b11 + a42 * b21 + a43 * b31;
		this.n24 = a41 * b12 + a42 * b22 + a43 * b32;
		this.n34 = a41 * b13 + a42 * b23 + a43 * b33;
		this.n44 = a41 * b14 + a42 * b24 + a43 * b34 + a44;
	}
	,
	calculateSkewSymmetric : function( a )
	{
		this.n11 = 0;
		this.n21 = - a.z;
		this.n31 = a.y;
		this.n12 = a.z;
		this.n22 = 0;
		this.n32 = - a.x;
		this.n13 = - a.y;
		this.n23 = a.x;
		this.n33 = 0;
	}
	,
	calculateAdd : function( a, b )
	{
		this.n11 = a.n11 + b.n11;
		this.n21 = a.n21 + b.n21;
		this.n31 = a.n31 + b.n31;
		this.n41 = a.n41 + b.n41;

		this.n12 = a.n12 + b.n12;
		this.n22 = a.n22 + b.n22;
		this.n32 = a.n32 + b.n32;
		this.n42 = a.n42 + b.n42;

		this.n13 = a.n13 + b.n13;
		this.n23 = a.n23 + b.n23;
		this.n33 = a.n33 + b.n33;
		this.n43 = a.n43 + b.n43;
	}
	,
	calculateSub : function( a, b )
	{
		this.n11 = a.n11 - b.n11;
		this.n21 = a.n21 - b.n21;
		this.n31 = a.n31 - b.n31;
		this.n41 = a.n41 - b.n41;

		this.n12 = a.n12 - b.n12;
		this.n22 = a.n22 - b.n22;
		this.n32 = a.n32 - b.n32;
		this.n42 = a.n42 - b.n42;

		this.n13 = a.n13 - b.n13;
		this.n23 = a.n23 - b.n23;
		this.n33 = a.n33 - b.n33;
		this.n43 = a.n43 - b.n43;
	}
	,
	calculateInverse3x3 : function( m )
	{
		var d = m.det();
		
		if( Math.abs(d) > 0.001 )
		{
			d = 1/d;
			
			var m11 = m.n11; var m21 = m.n12; var m31 = m.n21;
			var m12 = m.n21; var m22 = m.n22; var m32 = m.n23;
			var m13 = m.n31; var m23 = m.n32; var m33 = m.n33;
			var m14 = m.n41; var m24 = m.n42; var m34 = m.n43;
			
			this.n11 =	 d * ( m22 * m33 - m32 * m23 );
			this.n21 =	-d * ( m12 * m33 - m32 * m13 );
			this.n31 =	 d * ( m12 * m23 - m22 * m13 );
			this.n41 =	-d * ( m12 * (m23*m34 - m33*m24) - m22 * (m13*m34 - m33*m14) + m32 * (m13*m24 - m23*m14) );
			
			this.n12 =	-d * ( m21 * m33 - m31 * m23 );
			this.n22 =	 d * ( m11 * m33 - m31 * m13 );
			this.n32 =	-d* ( m11 * m23 - m21 * m13 );
			this.n42 =	 d * ( m11 * (m23*m34 - m33*m24) - m21 * (m13*m34 - m33*m14) + m31 * (m13*m24 - m23*m14) );
			
			this.n13 =	 d * ( m21 * m32 - m31 * m22 );
			this.n23 =	-d* ( m11 * m32 - m31 * m12 );
			this.n33 =	 d * ( m11 * m22 - m21 * m12 );
			this.n43 =	-d* ( m11 * (m22*m34 - m32*m24) - m21 * (m12*m34 - m32*m14) + m31 * (m12*m24 - m22*m14) );
		}
	}
	,
	calculateInverse4x4 : function( m )
	{
	    var data = m.getData();
	    var is = [0.0, 0.0, 0.0, 0.0];
	    var js = [0.0, 0.0, 0.0, 0.0];
	    var fDet = 1.0;
	    var f;
	    var k, i, j;
	
	    for ( k = 0; k < 4; k ++ )
	    {
	        var fMax = 0.0;
	        for ( i = k; i < 4; i ++ )
	        {
	            for ( j = k; j < 4; j ++ )
	            {
	                f = Math.abs( data[i][j] );
	                if ( f > fMax )
	                {
	                    fMax = f;
	                    is[k] = i;
	                    js[k] = j;
	                }
	            }
	        }
	        
	        $THROW( Math.abs( fMax ) >= 0.0, "矩阵求逆失败" );
	
	        if ( is[k] != k )
	        {
	            SmitearWebGL.Core.Math.swap( data[k][0], data[is[k]][0] );
	            SmitearWebGL.Core.Math.swap( data[k][1], data[is[k]][1] );
	            SmitearWebGL.Core.Math.swap( data[k][2], data[is[k]][2] );
	            SmitearWebGL.Core.Math.swap( data[k][3], data[is[k]][3] );
	        }
	        if ( js[k] != k )
	        {
	            SmitearWebGL.Core.Math.swap( data[0][k], data[0][js[k]] );
	            SmitearWebGL.Core.Math.swap( data[1][k], data[1][js[k]] );
	            SmitearWebGL.Core.Math.swap( data[2][k], data[2][js[k]] );
	            SmitearWebGL.Core.Math.swap( data[3][k], data[3][js[k]] );
	        }
	
	        fDet *= data[k][k];
	
	        data[k][k] = 1.0 / data[k][k];
	        for ( j = 0; j < 4; j ++ )
	        {
	            if ( j != k )
	            data[k][j] *= data[k][k];
	        }
	        for ( i = 0; i < 4; i ++ )
	        {
	            if ( i != k )
	            {
	                for ( j = 0; j < 4; j ++ )
	                {
	                    if ( j != k )
	                    data[i][j] = data[i][j] - data[i][k] * data[k][j];
	                }
	            }
	        }
	        for ( i = 0; i < 4; i ++ )
	        {
	            if ( i != k )
	            data[i][k] *= - data[k][k];
	        }
	    }
	
	    for ( k = 3; k >= 0; k -- )
	    {
	        if ( js[k] != k )
	        {
	            SmitearWebGL.Core.Math.swap( data[k][0], data[js[k]][0] );
	            SmitearWebGL.Core.Math.swap( data[k][1], data[js[k]][1] );
	            SmitearWebGL.Core.Math.swap( data[k][2], data[js[k]][2] );
	            SmitearWebGL.Core.Math.swap( data[k][3], data[js[k]][3] );
	        }
	        if ( is[k] != k )
	        {
	            SmitearWebGL.Core.Math.swap( data[0][k], data[0][is[k]] );
	            SmitearWebGL.Core.Math.swap( data[1][k], data[1][is[k]] );
	            SmitearWebGL.Core.Math.swap( data[2][k], data[2][is[k]] );
	            SmitearWebGL.Core.Math.swap( data[3][k], data[3][is[k]] );
	        }
	    }
	
	    this.reset( data );
	}
	,
	calculateTranspose : function()
	{
		var n11	 = this.n11;
		var n12	 = this.n12;
		var n13	 = this.n13;
		var n14	 = this.n14;

		var n21	 = this.n21;
		var n22	 = this.n22;
		var n23	 = this.n23;
		var n24	 = this.n24;

		var n31	 = this.n31;
		var n32	 = this.n32;
		var n33	 = this.n33;
		var n34	 = this.n34;

		var n41	 = this.n41;
		var n42	 = this.n42;
		var n43	 = this.n43;
		var n44	 = this.n44;

		this.n11 = n11;
		this.n21 = n12;
		this.n31 = n13;
		this.n41 = n14;
		this.n12 = n21;
		this.n22 = n22;
		this.n32 = n23;
		this.n42 = n24;
		this.n13 = n31;
		this.n23 = n32;
		this.n33 = n33;
		this.n43 = n34;
		this.n14 = n41;
		this.n24 = n42;
		this.n34 = n43;
		this.n44 = n44;
	}
	,
	billboard : function( to, up )
	{
		var from = $V3( this.n41, this.n42, this.n43 );
		var z = SmitearWebGL.Core.Math.Vector3.sub( from, to );
		z.normalize();
		var y = up.clone();
		y.normalize();
		var x = SmitearWebGL.Core.Math.Vector3.cross( y, z );
		z = SmitearWebGL.Core.Math.Vector3.cross( x, y );

		this.n11 = x.x;
		this.n12 = x.y;
		this.n13 = x.z;
		this.n14 = 0.0;
		this.n21 = y.x;
		this.n22 = y.y;
		this.n23 = y.z;
		this.n24 = 0.0;
		this.n31 = z.x;
		this.n32 = z.y;
		this.n33 = z.z;
		this.n34 = 0.0;
	}
	,
	identity : function()
	{
		this.n11 = this.n22 = this.n33 = this.n44 = 1.0;
		this.n12 = this.n13 = this.n14 = this.n21 = this.n23 =
		this.n24 = this.n31 = this.n32 = this.n34 = this.n41 =
		this.n42 = this.n43 = 0.0;
	}
	,
	det : function()
	{
		return	( this.n11 * this.n22 - this.n12 * this.n21 ) * this.n33 - ( this.n11 * this.n23 - this.n13 * this.n21 ) * this.n32 +
		( this.n12 * this.n23 - this.n13 * this.n22 ) * this.n31;
	}
	,
	//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * COPY * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

	copy : function( m )
	{
		this.n11 = m.n11;
		this.n21 = m.n21;
		this.n31 = m.n31;
		this.n41 = m.n41;

		this.n12 = m.n12;
		this.n22 = m.n22;
		this.n32 = m.n32;
		this.n42 = m.n42;

		this.n13 = m.n13;
		this.n23 = m.n23;
		this.n33 = m.n33;
		this.n43 = m.n43;

		return this;
	}
	,
	copy3x3 : function( m )
	{
		this.n11 = m.n11;
		this.n21 = m.n21;
		this.n31 = m.n31;
		this.n12 = m.n12;
		this.n22 = m.n22;
		this.n32 = m.n32;
		this.n13 = m.n13;
		this.n23 = m.n23;
		this.n33 = m.n33;

		return this;
	}
	,
	copy4x4 : function( m )
	{
		this.n11 = m.n11;
		this.n12 = m.n12;
		this.n13 = m.n13;
		this.n14 = m.n14;
		this.n21 = m.n21;
		this.n22 = m.n22;
		this.n23 = m.n23;
		this.n24 = m.n24;
		this.n31 = m.n31;
		this.n32 = m.n32;
		this.n33 = m.n33;
		this.n34 = m.n34;
		this.n41 = m.n41;
		this.n42 = m.n42;
		this.n43 = m.n43;
		this.n44 = m.n44;

		return this;
	}
	,
	clone : function()
	{
		return new SmitearWebGL.Core.Math.Matrix4x4( this.getData() );
	}
	,
	flatten : function ()
	{
		var rawData = [ this.n11, this.n12, this.n13, this.n14,
		this.n21, this.n22, this.n23, this.n24,
		this.n31, this.n32, this.n33, this.n34,
		this.n41, this.n42, this.n43, this.n44 ];

		return rawData;
	}
	,
	//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * trace * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	toString : function()
	{
		var s = "\n";

		s += Math.round( this.n11 * 1000 ) / 1000 + "\t\t" + Math.round( this.n21 * 1000 ) / 1000 + "\t\t" + Math.round( this.n31 * 1000 ) / 1000 + "\t\t" + Math.round( this.n41 * 1000 ) / 1000 + "\n";
		s += Math.round( this.n12 * 1000 ) / 1000 + "\t\t" + Math.round( this.n22 * 1000 ) / 1000 + "\t\t" + Math.round( this.n32 * 1000 ) / 1000 + "\t\t" + Math.round( this.n42 * 1000 ) / 1000 + "\n";
		s += Math.round( this.n13 * 1000 ) / 1000 + "\t\t" + Math.round( this.n23 * 1000 ) / 1000 + "\t\t" + Math.round( this.n33 * 1000 ) / 1000 + "\t\t" + Math.round( this.n43 * 1000 ) / 1000 + "\n";
		s += Math.round( this.n14 * 1000 ) / 1000 + "\t\t" + Math.round( this.n24 * 1000 ) / 1000 + "\t\t" + Math.round( this.n34 * 1000 ) / 1000 + "\t\t" + Math.round( this.n44 * 1000 ) / 1000 + "\n";

		return s;
	}
}
);

//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * CREATE * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

SmitearWebGL.Core.Math.Matrix4x4.create = function( data )
{
	return new SmitearWebGL.Core.Math.Matrix4x4( data );
}

//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * IDENTITY * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

SmitearWebGL.Core.Math.Matrix4x4.identity = function()
{
	return this.create();
}

//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * OPERATIONS * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

SmitearWebGL.Core.Math.Matrix4x4.multiply = function( a, b )
{
	var m = this.create();
	m.calculateMultiply( a, b );

	return m;
}

SmitearWebGL.Core.Math.Matrix4x4.multiply4x4 = function( a, b )
{
	var m = this.create();
	m.calculateMultiply4x4( a, b );

	return m;
}

SmitearWebGL.Core.Math.Matrix4x4.multiply3x3 = function( a, b )
{
	var m = this.create();
	m.calculateMultiply3x3( a, b );

	return m;
}

SmitearWebGL.Core.Math.Matrix4x4.add = function( a, b )
{
	var m = this.create();
	m.calculateAdd( a, b );

	return m;
}

SmitearWebGL.Core.Math.Matrix4x4.sub = function( a, b )
{
	var m = this.create();
	m.calculateSub( a, b );

	return m;
}

SmitearWebGL.Core.Math.Matrix4x4.inverse3x3 = function( m )
{
	var inv = this.create();
	inv.calculateInverse3x3( m );
	return inv;
}

SmitearWebGL.Core.Math.Matrix4x4.inverse4x4 = function( m )
{
	var inv = this.create();
	inv.calculateInverse4x4( m );
	return inv;
}

SmitearWebGL.Core.Math.Matrix4x4.flatten = function( m, dest )
{
	var rawData = m.flatten();

	for ( var i = 0; i < rawData.length; i ++ )
	dest[i] = rawData[i];

	return dest;
}

//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * VECTOR * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

SmitearWebGL.Core.Math.Matrix4x4.multiplyVector3 = function( out, m, v )
{
	var vx = v.x;
	var vy = v.y;
	var vz = v.z;

	out.x = vx * m.n11 + vy * m.n21 + vz * m.n31 + m.n41;
	out.y = vx * m.n12 + vy * m.n22 + vz * m.n32 + m.n42;
	out.z = vx * m.n13 + vy * m.n23 + vz * m.n33 + m.n43;
}

SmitearWebGL.Core.Math.Matrix4x4.multiplyVector4 = function( out, m, v )
{
	var vx = v.x;
	var vy = v.y;
	var vz = v.z;
	var vw = v.w;

	out.x = vx * m.n11 + vy * m.n21 + vz * m.n31 + vw * m.n41;
	out.y = vx * m.n12 + vy * m.n22 + vz * m.n32 + vw * m.n42;
	out.z = vx * m.n13 + vy * m.n23 + vz * m.n33 + vw * m.n43;
	out.w = vx * m.n14 + vy * m.n24 + vz * m.n34 + vw * m.n44;
}

SmitearWebGL.Core.Math.Matrix4x4.transformVector3 = function( out, m, v )
{
	var vx = v.x;
	var vy = v.y;
	var vz = v.z;
	var d = 1.0 / ( vx * m.n14 + vy * m.n24 + vz * m.n34 + m.n44 );

	out.x = (vx * m.n11 + vy * m.n21 + vz * m.n31 + m.n41) * d;
	out.y = (vx * m.n12 + vy * m.n22 + vz * m.n32 + m.n42) * d;
	out.z = (vx * m.n13 + vy * m.n23 + vz * m.n33 + m.n43) * d;
}

SmitearWebGL.Core.Math.Matrix4x4.transformVector4 = function( out, m, v )
{
	var vx = v.x;
	var vy = v.y;
	var vz = v.z;
	var vw = v.w;
	var d = 1.0 / ( vx * m.n14 + vy * m.n24 + vz * m.n34 + vw * m.n44 );

	out.x = (vx * m.n11 + vy * m.n21 + vz * m.n31 + vw * m.n41) * d;
	out.y = (vx * m.n12 + vy * m.n22 + vz * m.n32 + vw * m.n42) * d;
	out.z = (vx * m.n13 + vy * m.n23 + vz * m.n33 + vw * m.n43) * d;
	out.w = 1.0;
}

SmitearWebGL.Core.Math.Matrix4x4.rotateAxis = function( out, m, v )
{
	var vx = v.x;
	var vy = v.y;
	var vz = v.z;

	out.x = vx * m.n11 + vy * m.n21 + vz * m.n31;
	out.y = vx * m.n12 + vy * m.n22 + vz * m.n32;
	out.z = vx * m.n13 + vy * m.n23 + vz * m.n33;

	out.normalize();
}

SmitearWebGL.Core.Math.Matrix4x4.angleAxis = function( angle, axis )
{
	var xmx, ymy, zmz, xmy, ymz, zmx, xms, yms, zms;

	var x = axis.x;
	var y = axis.y;
	var z = axis.z;

	var cos = Math.cos( angle );
	var cosi = 1.0 - cos;
	var sin = Math.sin( angle );

	xms = x * sin;
	yms = y * sin;
	zms = z * sin;
	xmx = x * x;
	ymy = y * y;
	zmz = z * z;
	xmy = x * y;
	ymz = y * z;
	zmx = z * x;
	
	var m = SmitearWebGL.Core.Math.Matrix4x4.identity();

	m.n11 = ( cosi * xmx ) + cos;
	m.n12 = ( cosi * xmy ) + zms;
	m.n13 = ( cosi * zmx ) - yms;
	m.n14 = 0;
	m.n21 = ( cosi * xmy ) - zms;
	m.n22 = ( cosi * ymy ) + cos;
	m.n23 = ( cosi * ymz ) + xms;
	m.n24 = 0;
	m.n31 = ( cosi * zmx ) + yms;
	m.n32 = ( cosi * ymz ) - xms;
	m.n33 = ( cosi * zmz ) + cos;
	m.n34 = 0;
	m.n41 = 0;
	m.n42 = 0;
	m.n43 = 0;
	m.n44 = 1;

	return m;
}


//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ROTATION * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

SmitearWebGL.Core.Math.Matrix4x4.matrix2scale = function( m )
{
	var scale = $V3();
	var d = m.det();
	d /= Math.abs( d );

	scale.x = Math.sqrt( m.n11 * m.n11 + m.n21 * m.n21 + m.n31 * m.n31 ) * d;
	scale.y = Math.sqrt( m.n12 * m.n12 + m.n22 * m.n22 + m.n32 * m.n32 ) * d;
	scale.z = Math.sqrt( m.n13 * m.n13 + m.n23 * m.n23 + m.n33 * m.n33 ) * d;

	return scale;
}

SmitearWebGL.Core.Math.Matrix4x4.matrix2euler = function( m, euler )
{
	if ( m.n21 > 0.998 )
	{
		euler.x = 0;
		euler.y = Math.atan2( m.n13, m.n33 );
		euler.z = Math.PI / 2.0;
		return euler;
	}

	if ( m.n21 < -0.998 )
	{
		euler.x = 0;
		euler.y = Math.atan2( m.n13, m.n33 );
		euler.z = -Math.PI / 2.0;
		return euler;
	}

	euler.x = Math.atan2( -m.n23, m.n22 );
	euler.y = Math.atan2( -m.n31, m.n11 );
	euler.z = Math.asin( m.n21 );

	return euler;
}

//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * TRANSFORM * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

SmitearWebGL.Core.Math.Matrix4x4.rotationX = function( rad )
{
	var m = SmitearWebGL.Core.Math.Matrix4x4.identity();

	var c = Math.cos( rad );
	var s = Math.sin( rad );

	m.n22  =  c;
	m.n32  =  s;
	m.n23  = - s;
	m.n33 =  c;

	return m;
}

SmitearWebGL.Core.Math.Matrix4x4.rotationY = function( rad )
{
	var m = SmitearWebGL.Core.Math.Matrix4x4.identity();
	
	var c = Math.cos( rad );
	var s = Math.sin( rad );

	m.n11  =  c;
	m.n13  =  s;
	m.n31  = - s;
	m.n33 =  c;

	return m;
}

SmitearWebGL.Core.Math.Matrix4x4.rotationZ = function( rad )
{
	var m = SmitearWebGL.Core.Math.Matrix4x4.identity();
	
	var c = Math.cos( rad );
	var s = Math.sin( rad );

	m.n11 =  c;
	m.n12 = - s;
	m.n21 =  s;
	m.n22 =  c;

	return m;
}

SmitearWebGL.Core.Math.Matrix4x4.rotationMatrix = function( x, y, z )
{
	var mx = SmitearWebGL.Core.Math.Matrix4x4.rotationX(x);
	var my = SmitearWebGL.Core.Math.Matrix4x4.rotationY(y);
	var mz = SmitearWebGL.Core.Math.Matrix4x4.rotationZ(z);
	
	mx.calculateMultiply(my, mx);
	mx.calculateMultiply(mz, mx);
	
	return mx;
}

SmitearWebGL.Core.Math.Matrix4x4.scaleMatrix = function( x, y, z )
{
	var m = SmitearWebGL.Core.Math.Matrix4x4.identity();

	m.n11 = x;
	m.n22 = y;
	m.n33 = z;

	return m;
}

SmitearWebGL.Core.Math.Matrix4x4.translation = function( x, y, z )
{
	var m = SmitearWebGL.Core.Math.Matrix4x4.identity();

	m.n41 = x;
	m.n42 = y;
	m.n43 = z;

	return m;
}

SmitearWebGL.Core.Math.Matrix4x4.scale = function( x, y, z )
{
	var m = SmitearWebGL.Core.Math.Matrix4x4.identity();

	m.n11 = x;
	m.n22 = y;
	m.n33 = z;

	return m;
}

SmitearWebGL.Core.Math.Matrix4x4.decompose = function( m, position, rotation, scale )
{
	var normalized = $M4x4();

	var i = $V3( m.n11, m.n12, m.n13 );
	var j = $V3( m.n21, m.n22, m.n23 );
	var k = $V3( m.n31, m.n32, m.n33 );

	if( scale )
	{
		scale.x = i.modulo();
		scale.y = j.modulo();
		scale.z = k.modulo();
		
		if( scale.x == 0 || scale.y == 0 || scale.z == 0 )
		{
			$WARNING( "scale error" );
	
			return;
		}
	}
	
	if( position )
	{
		position.x = m.n41;
		position.y = m.n42;
		position.z = m.n43;
	}

	if( rotation )
	{
		if ( scale )
		{
			normalized.n11 = m.n11 / scale.x;
			normalized.n12 = m.n12 / scale.x;
			normalized.n13 = m.n13 / scale.x;
			normalized.n21 = m.n21 / scale.y;
			normalized.n22 = m.n22 / scale.y;
			normalized.n23 = m.n23 / scale.y;
			normalized.n31 = m.n31 / scale.z;
			normalized.n32 = m.n32 / scale.z;
			normalized.n33 = m.n33 / scale.z;
		}
		else
		{
			normalized.n11 = m.n11;
			normalized.n12 = m.n12;
			normalized.n13 = m.n13;
			normalized.n21 = m.n21;
			normalized.n22 = m.n22;
			normalized.n23 = m.n23;
			normalized.n31 = m.n31;
			normalized.n32 = m.n32;
			normalized.n33 = m.n33;
		}

//		var q = $Q();

		SmitearWebGL.Core.Math.Matrix4x4.matrix2euler( normalized, rotation );

		//q.setFromRotationMatrix( normalized );
		//q.getEuler( rotation );
	}
}

//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * PROJECTION * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *


SmitearWebGL.Core.Math.Matrix4x4.makePerspectiveFov = function( m, fovy, aspect, znear, zfar )
{
	var ymax = znear * Math.tan( fovy * Math.PI / 360.0 );
	var ymin = - ymax;
	var xmin = ymin * aspect;
	var xmax = - xmin;

	return SmitearWebGL.Core.Math.Matrix4x4.makePerspective( m, xmin, xmax, ymax, ymin, znear, zfar );
}

SmitearWebGL.Core.Math.Matrix4x4.makePerspective = function( m, left, right, top, bottom, znear, zfar )
{
	var X = 2 * znear / ( right - left );
	var Y = 2 * znear / ( top - bottom );
	var A = ( right + left ) / ( right - left );
	var B = ( top + bottom ) / ( top - bottom );
	var C = - ( zfar + znear ) / ( zfar - znear );
	var D = - 2 * zfar * znear / ( zfar - znear );
	
	m.n11 = X;
	m.n12 = m.n13 = m.n14 = 0.0;
	m.n22 = Y;
	m.n21 = m.n23 = m.n24 = 0.0;
	m.n31 = A;
	m.n32 = B;
	m.n33 = C;
	m.n34 = -1.0;
	m.n43 = D;
	m.n41 = m.n42 = m.n44 = 0.0;

	return m;
}

SmitearWebGL.Core.Math.Matrix4x4.makeOrthoFov = function( m, fovy, aspect, znear, zfar )
{
	var ymax = znear * Math.tan( fovy * Math.PI / 360.0 );
	var ymin = - ymax;
	var xmin = ymin * aspect;
	var xmax = ymax * aspect;

	return SmitearWebGL.Core.Math.Matrix4x4.makeOrtho( m, xmin, xmax, ymax, ymin, znear, zfar );
}

SmitearWebGL.Core.Math.Matrix4x4.makeOrtho = function( m, left, right, top, bottom, znear, zfar )
{
	var tx = - ( right + left ) / ( right - left );
	var ty = - ( top + bottom ) / ( top - bottom );
	var tz = - ( zfar + znear ) / ( zfar - znear );
	
	m.n11 = 2 / ( right - left );
	m.n12 = m.n13 = m.n14 = 0.0;
	m.n22 = 2 / ( top - bottom );
	m.n21 = m.n23 = m.n24 = 0.0;
	m.n33 = - 2 / ( zfar - znear );
	m.n31 = m.n32 = m.n34 = 0.0;
	m.n41 = tx;
	m.n42 = ty;
	m.n43 = tz;
	m.n44 = 1.0;
	
	return m;
}
		
SmitearWebGL.Core.Math.Matrix4x4.lookAtRH = function( m, ex, ey, ez, cx, cy, cz, ux, uy, uz )
{
	var _x = SmitearWebGL.Core.Math.Matrix4x4._x;
	var _y = SmitearWebGL.Core.Math.Matrix4x4._y;
	var _z = SmitearWebGL.Core.Math.Matrix4x4._z;
	var _m = SmitearWebGL.Core.Math.Matrix4x4._m;
	
	_z.reset( ex, ey, ez );	//eye
	_x.reset( cx, cy, cz );	//at
	_y.reset( ux, uy, uz );	//up;
	
	_z.subtract( _x );
	_z.normalize();
	_x.copy( _z );	//x=z
	_y.cross( _z );
	_y.normalize();	//y=x
	_z.cross( _y );	//z=y
	_z.normalize();
	
	m.n11 = _y.x;
	m.n12 = _z.x;
	m.n13 = _x.x;
	m.n14 = 0.0;
	m.n21 = _y.y;
	m.n22 = _z.y;
	m.n23 = _x.y;
	m.n24 = 0.0;
	m.n31 = _y.z;
	m.n32 = _z.z;
	m.n33 = _x.z;
	m.n34 = 0.0;
	m.n41 = 0.0;
	m.n42 = 0.0;
	m.n43 = 0.0;
	m.n44 = 1.0;
	
	_m.identity();
	_m.n41 = -ex;
	_m.n42 = -ey;
	_m.n43 = -ez;
	
	m.calculateMultiply( m, _m );
	
	return m;
}

SmitearWebGL.Core.Math.Matrix4x4.lookAtLH = function( m, ex, ey, ez, cx, cy, cz, ux, uy, uz )
{
	var _x = SmitearWebGL.Core.Math.Matrix4x4._x;
	var _y = SmitearWebGL.Core.Math.Matrix4x4._y;
	var _z = SmitearWebGL.Core.Math.Matrix4x4._z;
	var _m = SmitearWebGL.Core.Math.Matrix4x4._m;
	
	_z.reset( cx, cy, cz );	//at
	_x.reset( ex, ey, ez );	//eye
	_y.reset( ux, uy, uz );	//up;
	
	_z.subtract( _x );
	_z.normalize();
	_x.copy( _z );	//x=z
	_y.cross( _z );
	_y.normalize();	//y=x
	_z.cross( _y );	//z=y
	_z.normalize();
	
	m.n11 = _y.x;
	m.n12 = _z.x;
	m.n13 = _x.x;
	m.n14 = 0.0;
	m.n21 = _y.y;
	m.n22 = _z.y;
	m.n23 = _x.y;
	m.n24 = 0.0;
	m.n31 = _y.z;
	m.n32 = _z.z;
	m.n33 = _x.z;
	m.n34 = 0.0;
	m.n41 = 0.0;
	m.n42 = 0.0;
	m.n43 = 0.0;
	m.n44 = 1.0;
	
	_m.identity();
	_m.n41 = -ex;
	_m.n42 = -ey;
	_m.n43 = -ez;
	
	m.calculateMultiply( m, _m );
	
	return m;
}

SmitearWebGL.Core.Math.Matrix4x4.makeLookAt = function( m, ex, ey, ez, cx, cy, cz, ux, uy, uz )
{
	var eye = $V3( ex, ey, ez );
	var center = $V3( cx, cy, cz );
	var up = $V3( ux, uy, uz );

	var z = SmitearWebGL.Core.Math.Vector3.sub( eye, center );
	z.normalize();
	var x = SmitearWebGL.Core.Math.Vector3.cross( z, up );
	x.normalize();
	var y = SmitearWebGL.Core.Math.Vector3.cross( x, z );
	y.normalize();

	m.reset( [[x.x, y.x, z.x, 0],
	[x.y, y.y, z.y, 0],
	[x.z, y.z, z.z, 0],
	[0, 0, 0, 1]] );

	var t = $M4x4( [[1, 0, 0, 0],
					[0, 1, 0, 0],
					[0, 0, 1, 0],
					[- ex, - ey, - ez, 1]] );

	m.calculateMultiply( m, t );

	return m;
}

//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

var $M4x4 = SmitearWebGL.Core.Math.Matrix4x4.create;

SmitearWebGL.Core.Math.Matrix4x4._x = $V3();
SmitearWebGL.Core.Math.Matrix4x4._y = $V3();
SmitearWebGL.Core.Math.Matrix4x4._z = $V3();
SmitearWebGL.Core.Math.Matrix4x4._m = $M4x4();